from datetime import datetime
from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.ext import db
from looptube.utils import fetchYouTubeVideoStatus, fetchYouTubeVideoData, fetchYouTubeVideoFLVURL, searchYouTubeFirstPlayableVideo, fetchWebPage
from time import time, mktime
import math
from datetime import datetime

class VideoInfo(db.Model):
    source = db.StringProperty(required=True)
    videoID = db.StringProperty(required=True)
    title = db.StringProperty()
    description = db.TextProperty()
    thumbURL = db.StringProperty()
    duration  = db.IntegerProperty()
    sourceURL = db.StringProperty()
    flvURL = db.StringProperty()
    countTotal = db.IntegerProperty(default=0)
    countToday = db.IntegerProperty(default=0)
    lastPlayed = db.DateTimeProperty()
    recentlyLooped = db.BooleanProperty(default=False)

    @classmethod
    def getVideoInfo(cls, source, videoID, noshare=False, norecord=None):
        if (None == norecord):
            norecord = noshare
        videoStatus = fetchYouTubeVideoStatus(videoID)
        if ((None == videoStatus) or ('fail' == videoStatus['status'])):
            return None

        # TODO: following code should run in tracsaction
        videoInfos = VideoInfo.gql("WHERE source = :source AND videoID = :videoID LIMIT 1",
                                  source='youtube', videoID=videoID)
        if (videoInfos.count() > 0):
            videoInfo = videoInfos.fetch(1)[0]
        else:
            videoInfo = db.run_in_transaction(cls.create, source, videoID, norecord)
        # end
        
        videoInfo = videoInfo.update(noshare, norecord)
        
        return videoInfo

    @staticmethod
    def getRecentVideos(limit, offset=0):
        videoInfos = VideoInfo.gql("WHERE recentlyLooped = True ORDER BY lastPlayed DESC LIMIT %d" % (int(limit)+int(offset)))
        return videoInfos.fetch(limit, offset)
    
    @staticmethod
    def getTopVideos(limit, offset=0):
        videoInfos = VideoInfo.gql("WHERE countTotal > 0 ORDER BY countTotal DESC LIMIT %d" % (int(limit)+int(offset)))
        return videoInfos.fetch(limit, offset)

    @staticmethod
    def create(source, videoID, norecord=False):
        videoInfo = VideoInfo(source=source,
                              videoID=videoID,
                              countTotal=0,
                              lastPlayed=datetime.now(),
                              )
        if (not norecord):
            videoInfo.put()
        
        return videoInfo

    def increaseCount(self):
        if (self.countTotal):
            self.countTotal = self.countTotal + 1;
        else:
            self.countTotal = 1;
        if (self.countToday):
            self.countToday = self.countToday + 1;
        else:
            self.countToday = 1;
        self.lastPlayed = datetime.now()
        self.recentlyLooped = True
        self.put()
        return self

    def update(self, noshare=False, norecord=None):
        if (None == norecord):
            norecord = noshare
        if ((None == self.title) or (None == self.flvURL)):
            # fetch youtube video data
            if ('youtube' == self.source):
                if (None == self.title):
                    data = fetchYouTubeVideoData(self.videoID)
                    if (data and (len(data) > 0)):
                        self.title = data[0]['title']
                        self.description = data[0]['description']
                        self.thumbURL = data[0]['thumbURL']
                        self.duration = data[0]['duration']
                        self.sourceURL = data[0]['sourceURL']
                        self.author = data[0]['author']
                if (None == self.flvURL):
                    data = fetchYouTubeVideoFLVURL(self.videoID)
                    if (data):
                        self.flvURL = data['flvURL']
                if (not (norecord or (None == self.title))):
                    self.put() 
        if (not noshare):
            db.run_in_transaction(self.increaseCount)
            user = users.get_current_user()
            if (user and None != self.title):
                UserLoopedVideo.updateUserLoopedVideo(user, self)
        
        return self
    
class UserLoopedVideo(db.Model):
    user = db.UserProperty()
    video = db.ReferenceProperty(VideoInfo)
    lastPlayed = db.DateTimeProperty()

    @staticmethod
    def getUserRecentLoopedVideos(user, limit):
        if (None == user):
            return []
        userLoopedVideos = UserLoopedVideo.gql("WHERE user = :user ORDER BY lastPlayed DESC LIMIT %d" % int(limit),
                                               user=user)
        return userLoopedVideos.fetch(limit)

    @staticmethod
    def updateUserLoopedVideo(user, videoInfo):
        if (None == user):
            return
        userLoopedVideos = UserLoopedVideo.gql("WHERE user = :user AND video = :video LIMIT 1",
                                              user=user, video=videoInfo)
        if (userLoopedVideos.count() > 0):
            userLoopedVideo = userLoopedVideos.fetch(1)[0]
            userLoopedVideo.lastPlayed = datetime.now()
        else:
            userLoopedVideo = UserLoopedVideo(user=user,
                                              video=videoInfo,
                                              lastPlayed=datetime.now()
                                              )
        userLoopedVideo.put()

class Playlist(db.Model):
    title = db.StringProperty(required=True)
    videoIDs = db.StringProperty(default='')
    numVideos = db.IntegerProperty(default=0)
    user = db.UserProperty()
    added = db.DateTimeProperty(auto_now_add=True)
    lastPlayed = db.DateTimeProperty(auto_now_add=True)
    channelPlaylist = db.BooleanProperty(default=False)
    channelName = db.StringProperty(default='')
    countTotal = db.IntegerProperty(default=0)
    countToday = db.IntegerProperty(default=0)
    
    @staticmethod
    def getUserPlaylist(user):
        if (None == user):
            return []
        playlists = Playlist.gql('WHERE user=:user ORDER BY added', user=user)
        return playlists.fetch(playlists.count())
    
    def getKey(self):
        return str(self.key())
    
    @staticmethod
    def getRecentCreatedPlaylists(limit=5):
        playlists = Playlist.gql('WHERE channelPlaylist = False ORDER BY added DESC LIMIT %d' % int(limit))
        return playlists.fetch(limit)
    
    @staticmethod
    def getTopPlaylists(limit=5):
        playlists = Playlist.gql('WHERE channelPlaylist = False ORDER BY countTotal DESC LIMIT %d' % int(limit))
        return playlists.fetch(limit)

    def increaseCount(self):
        if (self.countTotal):
            self.countTotal = self.countTotal + 1;
        else:
            self.countTotal = 1;
        if (self.countToday):
            self.countToday = self.countToday + 1;
        else:
            self.countToday = 1;
        self.lastPlayed = datetime.now()
        self.put()
        return self
    
    def putVideoIDs(self, vids=None):
        if (None == vids):
            vids = self.videoIDs
        
        playlistVideos = PlaylistVideo.getPlaylistVideos(self, getIDOnly=False)
        db.delete(playlistVideos)
        
        videoIDList = vids.split(',')
        playlistVideos = []
        for i in range(0,len(videoIDList)):
            videoID = videoIDList[i]
            videoInfo = VideoInfo.getVideoInfo('youtube', videoID, noshare=True, norecord=False)
            playlistVideo = PlaylistVideo(playlist=self, videoID=videoID, videoInfo=videoInfo, order=i)
            playlistVideos.append(playlistVideo)
        db.put(playlistVideos)
        self.videoIDs = ''
        self.numVideos = len(videoIDList)
        self.put()
        return self

    def getVideoIDs(self, delim=','):
        playlistVideos = PlaylistVideo.getPlaylistVideos(self, getIDOnly=True)
        if (delim):
            return delim.join(playlistVideos)
        else:
            return playlistVideos

    @staticmethod
    def create(title, videoIDs, user=None, channelName=None):
        if (None == channelName):
            channelPlaylist = False
            channelName = ''
        else:
            channelPlaylist = True
        playlist = Playlist(title=title, user=user, channelPlaylist=channelPlaylist, channelName=channelName)
        playlist.put()
        playlist.putVideoIDs(videoIDs)
        return playlist
    
    def deletePlaylist(self):
        playlistVideos = PlaylistVideo.getPlaylistVideos(self, getIDOnly=False)
        db.delete(playlistVideos)
        self.delete()
                
class PlaylistVideo(db.Model):
    playlist = db.ReferenceProperty(Playlist)
    videoID = db.StringProperty(required=True)
    videoInfo = db.ReferenceProperty(VideoInfo)
    order = db.IntegerProperty(required=True)
    
    @classmethod
    def getPlaylistVideos(cls, pl, getIDOnly):
        if (Playlist != pl.__class__):
            return None
        playlistVideos = cls.gql('WHERE playlist=:1 ORDER BY order', pl)
        playlistVideos = playlistVideos.fetch(playlistVideos.count())
        if (False == getIDOnly):
            return playlistVideos
        
        videoIDs = []
        for playlistVideo in playlistVideos:
            if (playlistVideo.videoID in videoIDs):
                playlistVideo.delete()
            else:
                videoIDs.append(playlistVideo.videoID)
        return videoIDs
    
    def getVideoPlaylists(self, vinfo=None, vid=None):
        if (None == vinfo):
            if (None == vid):
                return None
            else:
                vinfo = VideoInfo.getVideoInfo('youtube', vid, noshare=True, norecord=False)
                if (vinfo != None and vinfo.title != None):
                    self.videoInfo = vinfo
                    self.put()
        playlists = []
        playlistVideos = cls.gql('WHERE playlist=:1 ORDER BY order', pl)
        playlistVideos = playlistVideos.fetch(playlistVideos.count())
        for playlistVideo in playlistVideos:
            playlists.append(playlistVideos.playlist)

        return playlists

class CronJob(db.Model):
    name = db.StringProperty(required=True)
    interval = db.IntegerProperty(required=True)
    lastRun = db.DateTimeProperty()

    @staticmethod 
    def get():
        cronjobs = CronJob.all()
        cronjobs = CronJob.fetch(len(cronJobs))
        return cronjobs

    @staticmethod 
    def checkInterval(lastRun, interval):
        now = time()
        lastRunTime = mktime(lastRun.timetuple())
        now_divided = math.floor(now / interval)
        lastRun_divided = math.floor(lastRunTime / interval)
        if (now_divided > lastRun_divided):
            return True
        else:
            return False

class RadioLog(db.Model):
    radioName = db.StringProperty(required=True)
    artist = db.StringProperty()
    song = db.StringProperty()
    videoInfo = db.ReferenceProperty(VideoInfo)
    hasVideoInfo = db.BooleanProperty(default=False)
    added = db.DateTimeProperty(auto_now_add=True)

    @staticmethod
    def get(radioName, limit, offset=0, hasVideoInfo=True):
        queryStr = ("WHERE radioName = :radioName AND hasVideoInfo = :hasVideoInfo " 
                    + "ORDER BY added DESC LIMIT %d" % int(limit))
        if (offset > 0):
            queryStr = queryStr + (" OFFSET %d" % int(offset))
        radioLogs = RadioLog.gql(queryStr, radioName=radioName, hasVideoInfo=hasVideoInfo)
        return radioLogs.fetch(limit)
    
    @staticmethod 
    def insert_in_transaction(radioName, artist, song, videoInfo=None):
        radioLog = RadioLog(radioName=radioName, artist=artist, song=song, videoInfo=videoInfo, hasVideoInfo=(not (None == videoInfo)))
        radioLog.put()
        return radioLog
    
    @classmethod
    def insert(cls, radioName, artist, song, videoInfo=None):
        lockResult = False
        while (False == lockResult):
            lockResult = memcache.add('DBLock', 1, 2)
            
        radioLogs = RadioLog.gql("WHERE radioName = :radioName ORDER BY added DESC LIMIT 1", radioName=radioName)
        if (radioLogs.count() > 0):
            radioLog = radioLogs.fetch(1)[0]
            if (radioLog.song == song):
                return radioLog
        radioLog = db.run_in_transaction(cls.insert_in_transaction, radioName, artist, song, videoInfo)

        memcache.delete('DBLock')
        return radioLog

    @staticmethod
    def logRadio(radioName, artist, song):
        radioLogs = RadioLog.gql("WHERE radioName=:radioName ORDER BY added DESC LIMIT 1", radioName=radioName)
        if (radioLogs.count() > 0):
            radioLog = radioLogs.fetch(1)[0]
            if (radioLog.song == song):
                return 'Same song now!'
    
        if (len(song) > 0):
            videoData = searchYouTubeFirstPlayableVideo("%s %s (music video OR lyrics)" % (artist, song))
            if (None == videoData):
                videoInfo = None
            else:
                videoInfo = VideoInfo.getVideoInfo("youtube", videoData['videoID'], False)
            radioLog = RadioLog.insert(radioName, artist, song, videoInfo)
            return 'Now playing: '+artist+': '+song
        else:
            return 'Empty result!'

    @staticmethod
    def getNumRadioRecentVideoInfos(radioName):
        radioLogs = RadioLog.gql("WHERE radioName = :radioName AND hasVideoInfo = True ORDER BY added DESC", radioName=radioName)
        return radioLogs.count()

    @staticmethod
    def getRadioThisHourVideoInfos(radioName):
        now = time()
        hourbase = now - 3600
        dt = datetime.fromtimestamp(hourbase) 
        radioLogs = RadioLog.gql((("WHERE radioName = :radioName AND hasVideoInfo = True" 
                                   + " AND added >= DATETIME(%d, %d, %d, %d, %d, %d)" 
                                   + " ORDER BY added DESC") 
                                   % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second))
                                  , radioName=radioName)
        radioLogs = radioLogs.fetch(radioLogs.count())
        videoInfos = []
        for radioLog in radioLogs:
            if (radioLog.videoInfo and radioLog.videoInfo.title and radioLog.videoInfo not in videoInfos):
                videoInfos.append(radioLog.videoInfo)
        
        return videoInfos
    
    @staticmethod
    def getRadioRecentVideoInfos(radioName):
        radioLogs = RadioLog.gql(("WHERE radioName = :radioName AND hasVideoInfo = True ORDER BY added DESC LIMIT %d" % (int(limit)+int(offset))), radioName=radioName)
        radioLogs = radioLogs.fetch(limit, offset)
        videoInfos = []
        for radioLog in radioLogs:
            videoInfos.append(radioLog.videoInfo)
        
        return videoInfos

class VideoStatus(db.Model):
    source = db.StringProperty(required=True)
    videoID = db.StringProperty(required=True)
    status = db.StringProperty(required=True)
    added = db.DateTimeProperty(auto_now_add=True)

    @staticmethod
    def addVideoStatus(videoID, status, source="YouTube"):
        videoStatus = VideoStatus(videoID=videoID, status=status, source=source)
        videoStatus.put()
        return videoStatus

    @staticmethod
    def fetchYouTubeVideoStatus(videoID):
        url = ('http://www.youtube.com/get_video_info?&video_id=' + videoID + '&el=embedded&ps=default'
               + '&eurl=http%3A%2F%2Flooptube%2Eappspot%2Ecom%2Floop%2Fyoutube%2F' + videoID)
        page = fetchWebPage(url)
        if ('' == page):
            return None
    
        dict = {}
        pairs = page.split('&')
        for pair in pairs:
            seg = pair.split('=')
            dict[seg[0]] = seg[1]
    
        return {'status' : dict['status']}

    @classmethod
    def getVideoStatus(cls, videoID, default='ok', source='YouTube'):
        videoStatuses = cls.gql('WHERE source=:source AND videoID=:videoID ORDER BY added DESC', source=source, videoID=videoID)
        if (videoStatuses.count() > 0):
            videoStatus = videoStatuses.fetch(1)[0]
        else:
            statusDict = cls.fetchYouTubeVideoStatus(videoID)
            if (None == statusDict):
                return {'status' : default}
            videoStatus = cls.addVideoStatus(videoID, statusDict['status'])
        return {'status' : videoStatus.status} 